import Vector2 from "../../src/maths/Vector2";
import Sprite2DApplication from "../../src/spriteSystem/Sprite2DApplication";
import IShape from "../../src/interfaces/IShape";
import SpriteFactory from "../../src/factorys/SpriteFactory";
import ISprite from "../../src/interfaces/ISprite";
import CanvasMouseEvent from "../../src/events/CanvasMouseEvent";
import EInputEventType from "../../src/enums/EInputEventType";
import CanvasKeyBoardEvent from "../../src/events/CanvasKeyBoardEvent";
import EOrder from "../../src/enums/EOrder";
import Math2D from "../../src/maths/Math2D";
import ERenderType from "../../src/enums/ERenderType";

export default class TankFollowBezierPathDemo {
    // 指向入口类
    private _app: Sprite2DApplication;
    // 贝塞尔路径相关成员变量
    private _curvePts: Vector2[];//存储所有锚点和控制点的数组
    private _bezierPath !: IShape;// 使用上面点集数组创建的二次贝塞尔形体对象
    // 共享的几何Shape，用来标记贝塞尔曲线的锚点和控制点
    private _circle: IShape;
    private _rect: IShape;
    // 标记变量，用来指示曲线封闭路径是否生成了
    // 需要使用这个变量来做一些很重要的操作
    private _addPointEnd: boolean;
    // 当前坦克沿着路径运行时的速度变量
    private _speed: number;
    // 整个Demo中，涉及的算法的核心变量，在程序中会详解
    private _curveIndex: number;
    private _curveParamT: number;

    private _postion: Vector2;
    // 用于插值计算当前的tank位置算出坦克正确的朝向
    private _lastPosition: Vector2;

    public constructor(app: Sprite2DApplication) {
        this._app = app;
        // 初始化为false，表示目前没有形成封闭的路径
        this._addPointEnd = false;
        // 如果生成坦克，初始化时，坦克位于第一条贝塞尔曲线的t为0的插值点处！！！
        this._curveIndex = 0;
        this._curveParamT = 0;
        // 给两个位置变量分配内存
        this._postion = Vector2.create();
        this._lastPosition = Vector2.create();
        // 初始化运行速度变量为5，可以自行调整
        this._speed = 5;
        this._curvePts = [];
        // 创建两个享元类
        this._circle = SpriteFactory.createCircle(5);
        this._rect = SpriteFactory.createRect(10, 10, 0.5, 0.5);
        // 初始化时，在根节点grid精灵上挂接鼠标和键盘事件
        if (this._app.rootContainer.sprite !== undefined) {
            // 必须要bind，否则this指向错误
            this._app.rootContainer.sprite.mouseEvent = this.mouseEvent.bind(this);
            this._app.rootContainer.sprite.keyEvent = this.keyEvent.bind(this);
        }

        this._app.start();
    }

    private mouseEvent(spr: ISprite, evt: CanvasMouseEvent): void {
        if (evt.type === EInputEventType.MOUSEDOWN) {
            if (spr === this._app.rootContainer.sprite) {
                if (this._addPointEnd === true) {
                    return;
                }
                if (this._curvePts.length % 2 === 0) {
                    this.createBezierMarker(evt.canvasPosition.x, evt.canvasPosition.y, true);
                } else {
                    this.createBezierMarker(evt.canvasPosition.x, evt.canvasPosition.y, false);
                }
            }
        }
    }

    private keyEvent(spr: ISprite, evt: CanvasKeyBoardEvent): void {
        if (evt.type === EInputEventType.KEYUP) {
            if (evt.key === 'e') {
                if (this._addPointEnd === true) {
                    return;
                }
                if (this._curvePts.length > 3) {
                    if ((this._curvePts.length - 1) % 2 > 0) {
                        this._curvePts.push(this._curvePts[0]);
                        this._addPointEnd = true;
                        this.createBezierPath();
                        this._postion.x = this._curvePts[0].x;
                        this._postion.y = this._curvePts[0].y;
                        this.createTank(this._postion.x, this._postion.y, 80, 50, 80);
                    }
                }
            } else if (evt.key === 'r') {
                if (this._addPointEnd === true) {
                    this._addPointEnd = false;
                    this._curvePts = [];
                    this._app.rootContainer.removeAll(false);
                }
            }
        }
        else if (evt.type === EInputEventType.KEYPRESS) {
            if (evt.key === 'a') {
                if (this._addPointEnd === true) {
                    if (spr.name === 'turret') {
                        spr.rotation += 5;
                    }
                }
            } else if (evt.key === 's') {
                if (this._addPointEnd === true) {
                    if (spr.name === 'turret') {
                        spr.rotation -= 5;
                    }
                }
            }
        }
    }

    private updateEvent(spr: ISprite, mesc: number, diffSec: number, travelOrder: EOrder): void {
        if (travelOrder === EOrder.PREORDER) {
            this.updateCurveIndex(diffSec * 0.1);
            let a0: Vector2 = this._curvePts[this._curveIndex * 2];
            let a1: Vector2 = this._curvePts[this._curveIndex * 2 + 1];
            let a2: Vector2 = this._curvePts[this._curveIndex * 2 + 2];
            Vector2.copy(this._postion, this._lastPosition);
            Math2D.getQuadraticBezierVector(a0, a1, a2, this._curveParamT, this._postion);
            spr.x = this._postion.x;
            spr.y = this._postion.y;
            spr.rotation = Vector2.getOrientation(this._lastPosition, this._postion, false);
        }
    }

    private renderEvent(spr: ISprite, context: CanvasRenderingContext2D, renderOrder: EOrder): void {
        if (renderOrder === EOrder.POSTORDER) {
            context.save();
            context.translate(100, 0);
            context.beginPath();
            context.arc(0, 0, 5, 0, Math.PI * 2);
            context.fill();
            context.restore();
        } else {
            context.save();
            context.translate(80, 0);
            context.fillRect(-5, -5, 10, 10);
            context.restore();
        }
    }

    private getCurveCount(): number {
        let n: number = this._curvePts.length;
        if (n === 0) {
            return 0;
        }
        return (n - 1) / 2;
    }

    private updateCurveIndex(diffSec: number): void {
        this._curveParamT += this._speed * diffSec;
        if (this._curveParamT >= 1.0) {
            this._curveIndex++;
            this._curveParamT = this._curveParamT % 1.0;
        }

        if (this._curveIndex >= this.getCurveCount()) {
            this._curveIndex = 0;
        }
    }

    private createTank(x: number, y: number, width: number, height: number, gunLength: number): void {
        let shape: IShape = SpriteFactory.createRect(width, height, 0.5, 0.5);
        let tank: ISprite = SpriteFactory.createISprite(shape, x, y, 0, 1, 1);
        tank.fillStyle = 'grey';
        tank.name = "tank";
        //tank . renderType = ERenderType . CLIP ;
        this._app.rootContainer.addSprite(tank);

        shape = SpriteFactory.createEllipse(15, 10);
        let turret: ISprite = SpriteFactory.createISprite(shape);
        turret.fillStyle = 'red';
        turret.name = "turret";
        turret.keyEvent = this.keyEvent.bind(this);
        tank.owner.addSprite(turret);

        shape = SpriteFactory.createLine(Vector2.create(0, 0), Vector2.create(gunLength, 0));
        let gun: ISprite = SpriteFactory.createISprite(shape);
        gun.strokeStyle = 'blue';
        gun.lineWidth = 3;
        gun.name = 'gun';
        gun.renderEvent = this.renderEvent.bind(this);
        turret.owner.addSprite(gun);

        //tank . owner . addSprite ( SpriteFactory . createClipSprite ( ) ) ;

        if (tank.owner.sprite !== undefined) {
            tank.owner.sprite.updateEvent = this.updateEvent.bind(this);
        }
    }
    // 生成锚点或控制点精灵代码
    private createBezierMarker(x: number, y: number, isCircle: boolean): void {
        let idx: number = this._curvePts.length;
        // 1. 将当前点坐标添加到_curvePts数组中
        this._curvePts.push(Vector2.create(x, y));
        // 2. 创建精灵对象
        let sprite: ISprite;
        if (isCircle) {
            sprite = SpriteFactory.createSprite(this._circle);
            sprite.fillStyle = 'blue';
        } else {
            sprite = SpriteFactory.createSprite(this._rect);
            sprite.fillStyle = 'red';
        }
        // 鼠标点击处的位置坐标
        sprite.x = x;
        sprite.y = y;
        sprite.name = "curvePt" + this._curvePts.length;
        this._app.rootContainer.addSprite(sprite);
        // 3. 给精灵对象添加drag事件，使用匿名箭头函数，this指针永远正确！！
        sprite.mouseEvent = (spr: ISprite, evt: CanvasMouseEvent): void => {
            if (evt.type === EInputEventType.MOUSEDRAG) {
                spr.x = evt.canvasPosition.x;
                spr.y = evt.canvasPosition.y;
                // 当拖动时，会实时地更新贝塞尔曲线上对应的锚点或控制点的坐标
                // 这样就能调整当前某条曲线线段的曲率或位置
                this._curvePts[idx].x = spr.x;
                this._curvePts[idx].y = spr.y;
            }
        }
    }

    private createBezierPath(): void {
        this._bezierPath = SpriteFactory.createBezierPath(this._curvePts);
        let sprite: ISprite = SpriteFactory.createSprite(this._bezierPath);
        sprite.strokeStyle = 'blue';
        sprite.renderType = ERenderType.STROKE;
        sprite.name = "bezierPath";
        this._app.rootContainer.addSprite(sprite);
        for (let i: number = 1; i < this._curvePts.length; i += 2) {
            this.createLine(this._curvePts[i - 1], this._curvePts[i], i);
        }
    }
    // 生成起始锚点和控制点之间连线精灵代码
    // idx没有特别的意义，只是用于debug时显示名字
    private createLine(start: Vector2, end: Vector2, idx: number): void {
        let line: ISprite = SpriteFactory.createISprite(SpriteFactory.createLine(start, end), 0, 0);
        line.lineWidth = 2;
        line.strokeStyle = 'green';
        line.name = "line" + idx;
        this._app.rootContainer.addSprite(line);
    }
}